package datastructure

import (
	"encoding/json"
	"fmt"
	"math/big"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"strings"
	"io/ioutil"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/OpenBazaar/openbazaar-go/core"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"path"
	"strconv"
	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/ipfs"
	"time"
	"github.com/OpenBazaar/wallet-interface"
	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/proto"
)

type CustomOrder struct {
	ID                *big.Int		`json:"id,omitempty"`
	BuyerID           string		`json:"buyerID,omitempty"`
	VendorID          string		`json:"vendorID,omitempty"`
	Payment           Payment		`json:"payment,omitempty"`
	Listings          []Listing		`json:"listings,omitempty"`
	Shipping          Shipping		`json:"shipping,omitempty"`
	Other             Other			`json:"other,omitempty"`
}
type Payment struct {
	BuyerAddress      string
	VendorAddress     string
	TotalItem         uint64
	TotalShipping     uint64
	Total             uint64
}
type Listing struct {
	ListingHash       string        `json:"listingHash,omitempty"`
	Tiny              string        `json:"-"`
	Price             uint64        `json:"-"`
	Quantity          uint32        `json:"quantity,omitempty"`
	Memo              string        `json:"memo,omitempty"`
}
type Shipping struct {
	ShipTo             string       `json:"shipTo,omitempty"`
	Address            string       `json:"address,omitempty"`
	City               string       `json:"city,omitempty"`
	State              string       `json:"state,omitempty"`
	TotalShipping      uint64       `json:"totalShipping,omitempty"`
	PostalCode         string       `json:"postalCode,omitempty"`
	AddressNotes       string       `json:"addressNotes,omitempty"`
	ShipmentNumber     string       `json:"shipmentNumber,omitempty"`
}

const TimeTail = ".9744584 +0800 CST"
type Other struct {
	CreateTime         string       `json:"createTime, omitempty"`
	ConfirmTime        string       `json:"confirmTime, omitempty"`//卖家Accept  or  Reject
	CancelTime         string       `json:"cancelTime, omitempty"`//买家Cancel
	DeliveryTime       string       `json:"deliveryTime, omitempty"`
	CompleteTime       string       `json:"completeTime, omitempty"`//订单完成、成功退货或者成功收货
	RefundTime         string       `json:"refundTime, omitempty"`//买家退货
	AlternateContactInfo string     `json:"alternateContactInfo,omitempty"`
}

type OrderContract struct {
	Id            *big.Int
	BuyerId       string
	SellerId      string
	Buyer         common.Address
	Seller        common.Address
	TotalPrice     *big.Int
	GoodJson      string
	ShipJson      string
	OtherJson     string
}

type PurchaseData struct {
	ShipTo               string  `json:"shipTo"`
	Address              string  `json:"address"`
	City                 string  `json:"city"`
	State                string  `json:"state"`
	PostalCode           string  `json:"postalCode"`
	CountryCode          string  `json:"countryCode"`
	AddressNotes         string  `json:"addressNotes"`
	Moderator            string  `json:"moderator"`
	Items                []item  `json:"items"`
	AlternateContactInfo string  `json:"alternateContactInfo"`
	RefundAddress        *string `json:"refundAddress"` //optional, can be left out of json
}
type item struct {
	ListingHash string         `json:"listingHash"`
	Quantity    int            `json:"quantity"`
	Options     []option       `json:"options"`
	Shipping    shippingOption `json:"shipping"`
	Memo        string         `json:"memo"`
	Coupons     []string       `json:"coupons"`
}
type option struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}
type shippingOption struct {
	Name    string `json:"name"`
	Service string `json:"service"`
}

func (order *CustomOrder) ToPurchaseData(n *core.OpenBazaarNode) (*PurchaseData, error) {
	purchaseData := new(PurchaseData)
	purchaseData.ShipTo = order.Shipping.ShipTo
	purchaseData.City = order.Shipping.City
	purchaseData.State = order.Shipping.State
	purchaseData.Address = order.Shipping.Address
	purchaseData.PostalCode = order.Shipping.PostalCode
	purchaseData.CountryCode = "NA"
	purchaseData.AddressNotes = order.Shipping.AddressNotes
	purchaseData.Moderator = ""
	purchaseData.AlternateContactInfo = order.Other.AlternateContactInfo
	purchaseData.RefundAddress = nil
	items := new([]item)
	for _, item1 := range order.Listings {
		item := new(item)
		item.ListingHash = item1.ListingHash
		item.Quantity = int(item1.Quantity)
		item.Coupons = *(new([]string))
		item.Memo = item1.Memo
		item.Options = *new([]option)
		item.Shipping = shippingOption{" "," "}
		*items = append(*items, *item)
	}
	purchaseData.Items = *items
	return purchaseData, nil
}

func (data *PurchaseData) CreateContractWithOrder(n *core.OpenBazaarNode, timestring string) (*pb.RicardianContract, error) {
	contract := new(pb.RicardianContract)
	order := new(pb.Order)
	order.RefundAddress = n.Wallet.NewAddress(wallet.INTERNAL).EncodeAddress()
	shipping := &pb.Order_Shipping{
		ShipTo:       data.ShipTo,
		Address:      data.Address,
		City:         data.City,
		State:        data.State,
		PostalCode:   data.PostalCode,
		Country:      pb.CountryCode(pb.CountryCode_value[data.CountryCode]),
		AddressNotes: data.AddressNotes,
	}
	order.Shipping = shipping

	id := new(pb.ID)
	profile, err := n.GetProfile()
	if err == nil {
		id.Handle = profile.Handle
	}

	id.PeerID = n.IpfsNode.Identity.Pretty()
	pubkey, err := n.IpfsNode.PrivateKey.GetPublic().Bytes()
	if err != nil {
		return nil, err
	}
	keys := new(pb.ID_Pubkeys)
	keys.Identity = pubkey

	ecPubKey, err := n.Wallet.MasterPublicKey().ECPubKey()
	if err != nil {
		return nil, err
	}
	keys.Bitcoin = ecPubKey.SerializeCompressed()
	id.Pubkeys = keys
	// Sign the PeerID with the Bitcoin key
	ecPrivKey, err := n.Wallet.MasterPrivateKey().ECPrivKey()
	if err != nil {
		return nil, err
	}
	sig, err := ecPrivKey.Sign([]byte(id.PeerID))
	id.BitcoinSig = sig.Serialize()
	order.BuyerID = id
	times, _ := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", timestring)
	ts, err := ptypes.TimestampProto(times)
	if err != nil {
		return nil, err
	}
	order.Timestamp = ts
	order.AlternateContactInfo = data.AlternateContactInfo

	var ratingKeys [][]byte
	for range data.Items {
		// Also the fix for this will also need to be included in the rating signing code.
		ratingKey, err := n.Wallet.MasterPublicKey().Child(uint32(ts.Seconds))
		if err != nil {
			return nil, err
		}
		ecRatingKey, err := ratingKey.ECPubKey()
		if err != nil {
			return nil, err
		}
		ratingKeys = append(ratingKeys, ecRatingKey.SerializeCompressed())
	}
	order.RatingKeys = ratingKeys

	addedListings := make(map[string]*pb.Listing)
	for _, item := range data.Items {
		i := new(pb.Order_Item)

		_, exists := addedListings[item.ListingHash]

		listing := new(pb.Listing)
		if !exists {
			// Let's fetch the listing, should be cached
			b, err := ipfs.Cat(n.Context, item.ListingHash)
			if err != nil {
				return nil, err
			}
			sl := new(pb.SignedListing)
			err = jsonpb.UnmarshalString(string(b), sl)
			if err != nil {
				return nil, err
			}
			contract.VendorListings = append(contract.VendorListings, sl.Listing)
			s := new(pb.Signature)
			s.Section = pb.Signature_LISTING
			s.SignatureBytes = sl.Signature
			contract.Signatures = append(contract.Signatures, s)
			addedListings[item.ListingHash] = sl.Listing
			listing = sl.Listing
		} else {
			listing = addedListings[item.ListingHash]
		}

		ser, err := proto.Marshal(listing)
		if err != nil {
			return nil, err
		}
		listingId, err := core.EncodeCID(ser)
		if err != nil {
			return nil, err
		}
		i.ListingHash = listingId.String()
		i.Quantity = uint32(item.Quantity)

		for _, option := range item.Options {
			o := &pb.Order_Item_Option{
				Name:  option.Name,
				Value: option.Value,
			}
			i.Options = append(i.Options, o)
		}
		so := &pb.Order_Item_ShippingOption{
			Name:    item.Shipping.Name,
			Service: item.Shipping.Service,
		}
		i.ShippingOption = so
		i.Memo = item.Memo
		order.Items = append(order.Items, i)
	}
	contract.BuyerOrder = order
	return contract, nil
}

func (order *OrderContract) ToCustomOrder(n *core.OpenBazaarNode) (*CustomOrder, error) {
	shipping := new(Shipping)
	error := json.Unmarshal([]byte(order.ShipJson), shipping)
	if error != nil {
		return nil, error
	}
	totalPrice, error := strconv.ParseInt(order.TotalPrice.String(),10, 64)
	if error != nil {
		return nil, error
	}
	other := new(Other)
	error = json.Unmarshal([]byte(order.OtherJson), other)
	if error != nil {
		return nil, error
	}
	payment := Payment{
		order.Buyer.String(),
		order.Seller.String(),
		uint64(totalPrice)-shipping.TotalShipping,
		shipping.TotalShipping,
		uint64(totalPrice),
	}
	listings := new([]Listing)
	error = json.Unmarshal(([]byte)(order.GoodJson),listings)
	if error != nil {
		return nil, error
	}
	customOrder := &CustomOrder{
		order.Id,
		order.BuyerId,
		order.SellerId,
		payment,
		*listings,
		*shipping,
		*other,
	}
	return customOrder, nil
}

func QueryListing(n *core.OpenBazaarNode, listingHash string) (*pb.SignedListing, error){
	// Let's fetch the listing, should be cached
	b, err := ipfs.Cat(n.Context, listingHash)
	if err != nil {
		return nil, err
	}
	sl := new(pb.SignedListing)
	err = jsonpb.UnmarshalString(string(b), sl)
	if err != nil {
		return nil, err
	}
	return sl, nil
}

func (order *CustomOrder) ToOrderContract(n *core.OpenBazaarNode) (*OrderContract, error) {
	//商品信息
	_goodJson, error1 := json.Marshal(order.Listings)
	if error1!=nil {
		return nil, error1
	}
	//ship信息
	_shipJson, error2 := json.Marshal(order.Shipping)
	if error2!=nil {
		return nil, error2
	}
	//other信息
	_otherJson, error2 := json.Marshal(order.Other)
	if error2!=nil {
		return nil, error2
	}
	//封装并send合约
	var profile pb.Profile
	profile, error3 := n.GetProfile()
	if error3 != nil {
		return nil, error3
	}
	repoPath, error4 := repo.GetRepoPath(false)
	key, error4 := ioutil.ReadFile(path.Join(repoPath, "keystore", profile.Keypath))
	if error4 != nil {
		fmt.Println("%s", error4.Error())
	}
	var keydata string = string(key)
	auth, error5 := bind.NewTransactor(strings.NewReader(keydata),profile.Keywords)
	if error5 != nil {
		fmt.Println("%s", error5.Error())
	}
	orderContract := OrderContract{
		order.ID,
		order.BuyerID,
		order.VendorID,
		auth.From,
		common.HexToAddress(order.Payment.VendorAddress),
		big.NewInt(int64(order.Payment.Total)),
		string(_goodJson),
		string(_shipJson),
		string(_otherJson),
	}
	return &orderContract, nil
}

var OrderState_name = map[int32]string{
	0:  "PENDING",
	1:  "AWAITING_PAYMENT",
	2:  "AWAITING_PICKUP",
	3:  "AWAITING_FULFILLMENT",
	4:  "PARTIALLY_FULFILLED",
	5:  "FULFILLED",
	6:  "COMPLETED",
	7:  "CANCELED",
	8:  "DECLINED",
	9:  "REFUNDED",
	10: "DISPUTED",
	11: "DECIDED",
	12: "RESOLVED",
	13: "PAYMENT_FINALIZED",
}
var OrderState_value = map[string]int32{
	"PENDING":              0,
	"AWAITING_PAYMENT":     1,
	"AWAITING_PICKUP":      2,
	"AWAITING_FULFILLMENT": 3,
	"PARTIALLY_FULFILLED":  4,
	"FULFILLED":            5,
	"COMPLETED":            6,
	"CANCELED":             7,
	"DECLINED":             8,
	"REFUNDED":             9,
	"DISPUTED":             10,
	"DECIDED":              11,
	"RESOLVED":             12,
	"PAYMENT_FINALIZED":    13,
}
//0创建  1发货中  2完成 3退款完成  4退款中  5退货完成  6退货中 7卖家拒绝订单完成 8买家取消订单完成
func (c *CustomOrder) ToPurchase(n *core.OpenBazaarNode, state uint8) (*repo.Purchase, error) {
	timestamp, error := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", c.Other.CreateTime+TimeTail)
	if error != nil {
		fmt.Println("订单时间出错", error.Error())
		timestamp = time.Now()
	}
	sl, error := QueryListing(n, (c.Listings)[0].ListingHash)
	if error != nil {
		return nil, error
	}
	purchase := repo.Purchase{
		c.ID.String(),
		sl.Listing.Slug,
		timestamp,
		sl.Listing.Item.Title,
		sl.Listing.Item.Images[0].Tiny,
		c.Payment.Total,
		c.VendorID,
		"VendorHandle",
		c.Shipping.ShipTo,
		c.Shipping.Address,
		OrderState_name[int32(state)],
		false,
		false,
		0,
	}
	return &purchase, nil
}
func (c *CustomOrder) ToSale(n *core.OpenBazaarNode, state uint8) (*repo.Sale, error) {
	timestamp, error := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", c.Other.CreateTime+TimeTail)
	if error != nil {
		fmt.Println("订单时间出错", error.Error())
		timestamp = time.Now()
	}
	sl, error := QueryListing(n, (c.Listings)[0].ListingHash)
	if error != nil {
		return nil, error
	}
	sale := repo.Sale{
		c.ID.String(),
		sl.Listing.Slug,
		timestamp,
		sl.Listing.Item.Title,
		sl.Listing.Item.Images[0].Tiny,
		c.Payment.Total,
		c.BuyerID,
		"",
		c.Shipping.ShipTo,
		c.Shipping.Address,
		OrderState_name[int32(state)],
		false,
		false,
		0,
	}
	return &sale, nil
}